package http

import (
	"fmt"
	"github.com/dave-gray101/v2keyauth"
	"github.com/gofiber/contrib/fiberzerolog"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/csrf"
	"github.com/gofiber/fiber/v2/middleware/recover"
	"github.com/rs/zerolog/log"
	"shyxy-net/gateway/application"
	"shyxy-net/gateway/http/middleware"
	"shyxy-net/gateway/http/routes"
	"shyxy-net/gateway/http/schema"
	"shyxy-net/gateway/http/utils"
)

func API(application *application.Application) (*fiber.App, error) {
	appCfg := application.Config()
	fiberCfg := fiber.Config{
		BodyLimit:             1024 * 1024,
		DisableStartupMessage: true,
	}
	router := fiber.New(fiberCfg)
	allowHeaders := "*"
	if appCfg.CORS {
		var c func(ctx *fiber.Ctx) error
		if appCfg.CORSAllowOrigins == "" {
			c = cors.New(cors.Config{
				AllowOrigins:     cors.ConfigDefault.AllowOrigins,
				AllowMethods:     cors.ConfigDefault.AllowMethods,
				AllowHeaders:     allowHeaders,
				AllowCredentials: cors.ConfigDefault.AllowCredentials,
			})
		} else {
			c = cors.New(cors.Config{AllowOrigins: appCfg.CORSAllowOrigins,
				AllowMethods:     cors.ConfigDefault.AllowMethods,
				AllowHeaders:     allowHeaders,
				AllowCredentials: cors.ConfigDefault.AllowCredentials,
			})
		}

		router.Use(c)
	}
	router.Use(middleware.StripPathPrefix())

	router.Use(middleware.UserJWTAuth())

	if appCfg.MachineTag != "" {
		router.Use(func(c *fiber.Ctx) error {
			c.Response().Header.Set("Machine-Tag", appCfg.MachineTag)

			return c.Next()
		})
	}

	//router.Use("/v1/realtime", func(c *fiber.Ctx) error {
	//	if websocket.IsWebSocketUpgrade(c) {
	//		// Returns true if the client requested upgrade to the WebSocket protocol
	//		return c.Next()
	//	}
	//
	//	return nil
	//})

	router.Hooks().OnListen(func(listenData fiber.ListenData) error {
		scheme := "http"
		if listenData.TLS {
			scheme = "https"
		}
		log.Info().Str("endpoint", scheme+"://"+listenData.Host+":"+listenData.Port).Msg("OpenAI API is listening! Please connect to the endpoint for API documentation.")
		return nil
	})

	// Have Fiber use zerolog like the rest of the application rather than it's built-in logger
	logger := log.Logger
	router.Use(fiberzerolog.New(fiberzerolog.Config{
		Logger: &logger,
	}))

	// Default middleware config

	if !appCfg.Debug {
		router.Use(recover.New())
	}

	//if !appCfg.DisableMetrics {
	//	metricsService, err := services.NewLocalAIMetricsService()
	//	if err != nil {
	//		return nil, err
	//	}
	//	if metricsService != nil {
	//		router.Use(localai.LocalAIMetricsAPIMiddleware(metricsService))
	//		router.Hooks().OnShutdown(func() error {
	//			return metricsService.Shutdown()
	//		})
	//	}
	//}

	// Health Checks should always be exempt from auth, so register these first
	//routes.HealthRoutes(router)

	kaConfig, err := middleware.GetKeyAuthConfig(appCfg)
	if err != nil || kaConfig == nil {
		return nil, fmt.Errorf("failed to create key auth config: %w", err)
	}

	//httpFS := http.FS(embedDirStatic)

	//router.Use("/static", filesystem.New(filesystem.Config{
	//	Root:       httpFS,
	//	PathPrefix: "static",
	//	Browse:     true,
	//}))
	//
	//if appCfg.GeneratedContentDir != "" {
	//	os.MkdirAll(application.ApplicationConfig().GeneratedContentDir, 0750)
	//	audioPath := filepath.Join(application.ApplicationConfig().GeneratedContentDir, "audio")
	//	imagePath := filepath.Join(application.ApplicationConfig().GeneratedContentDir, "images")
	//	videoPath := filepath.Join(application.ApplicationConfig().GeneratedContentDir, "videos")
	//
	//	os.MkdirAll(audioPath, 0750)
	//	os.MkdirAll(imagePath, 0750)
	//	os.MkdirAll(videoPath, 0750)
	//
	//	router.Static("/generated-audio", audioPath)
	//	router.Static("/generated-images", imagePath)
	//	router.Static("/generated-videos", videoPath)
	//}

	// Auth is applied to _all_ endpoints. No exceptions. Filtering out endpoints to bypass is the role of the Filter property of the KeyAuth Configuration
	router.Use(v2keyauth.New(*kaConfig))

	if appCfg.CSRF {
		log.Debug().Msg("Enabling CSRF middleware. Tokens are now required for state-modifying requests")
		router.Use(csrf.New())
	}

	requestExtractor := middleware.NewRequestExtractor(appCfg, application.ServiceManager())

	routes.RegisterOpenAIRoutes(router, requestExtractor)

	router.Use(notFoundHandler)
	return router, nil
}

func notFoundHandler(c *fiber.Ctx) error {
	// Check if the request accepts JSON
	if string(c.Context().Request.Header.ContentType()) == "application/json" || len(c.Accepts("html")) == 0 {
		// The client expects a JSON response
		return c.Status(fiber.StatusNotFound).JSON(schema.ErrorResponse{
			Error: &schema.APIError{Message: "Resource not found", Code: fiber.StatusNotFound},
		})
	} else {
		// The client expects an HTML response
		return c.Status(fiber.StatusNotFound).Render("views/404", fiber.Map{
			"BaseURL": utils.BaseURL(c),
		})
	}
}
